package com.itheima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.common.constants.BusinessConstants;
import com.itheima.common.enums.HttpCodeEnum;
import com.itheima.common.exception.LeadNewsException;
import com.itheima.common.util.RequestContextUtil;
import com.itheima.common.vo.PageResultVo;
import com.itheima.wemedia.dto.AuthReqDto;
import com.itheima.wemedia.dto.DownOrUpDto;
import com.itheima.wemedia.dto.WmNewsContentNodeDto;
import com.itheima.wemedia.dto.WmNewsDtoSave;
import com.itheima.wemedia.dto.WmNewsPageReqDto;
import com.itheima.wemedia.mapper.WmMaterialMapper;
import com.itheima.wemedia.mapper.WmNewsMaterialMapper;
import com.itheima.wemedia.mapper.WmUserMapper;
import com.itheima.wemedia.pojo.WmMaterial;
import com.itheima.wemedia.pojo.WmNews;
import com.itheima.wemedia.mapper.WmNewsMapper;
import com.itheima.wemedia.pojo.WmNewsMaterial;
import com.itheima.wemedia.pojo.WmUser;
import com.itheima.wemedia.service.WmNewsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.wemedia.vo.WmNewsVo;
import org.springframework.beans.BeanUtils;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @description <p>自媒体图文内容信息 业务实现</p>
 *
 * @version 1.0
 * @package com.itheima.wemedia.service.impl
 */
@Service
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {

    @Resource
    private WmMaterialMapper wmMaterialMapper;

    @Resource
    private WmNewsMaterialMapper wmNewsMaterialMapper;

    @Resource
    private KafkaTemplate kafkaTemplate;

    @Resource
    private WmUserMapper wmUserMapper;

    /**
     * 发表文章
     * rollbackFor: 指定的异常时，事务回滚
     * propagation： 事务的传播行为 7
     * isolation: 隔离级别 4种
     * @param draft
     * @param dto
     */
    @Override
    @Transactional//(rollbackFor = , propagation = , isolation = )
    public void submit(Boolean draft, WmNewsDtoSave dto) {
        // 如果是修改时，只能修改自已的文章
        if(null != dto.getId()) {
            WmNews oldWmNews = findById(dto.getId());
            if (oldWmNews==null) {
                throw new LeadNewsException(HttpCodeEnum.DATA_NOT_EXIST);
            }
        }
        //1. 保存数据到wm_news表
        //1.1 构建wmNews对象
        WmNews pojo = buildWmNews(dto,draft);
        //1.2 如果前端提交的type(封面的类型)为自动(从文章内容中的图片来)时，处理一下
        //   规则： 内容中的图片数量
        //   数量>=3 时， 封面则有3张图
        //   数量>=1 时， 封面则有1张图
        //   反之 则无图
        setWmNewsType(pojo, dto);
        // 保存到新闻表里
        if(null == dto.getId()) {
            save(pojo);
        }else{
            // 修改
            updateById(pojo);
            // 删除旧关系  多对多， 关系的更新：先删除，再添加
            removeNewsMaterialRelationship(pojo.getId());
        }
        //2. 保存素材与wm_news_material的关系
        saveWmNewsMaterialRelationship(pojo);

        // 发送kafka消息，文章id去做自动审核
        if(!draft){
            // 非草稿，提交待审核
            // 文章id为long类型，但kefka的value是字符串序列化器，文章id要转成字符串
            kafkaTemplate.send(BusinessConstants.MqConstants.WM_NEWS_AUTO_SCAN_TOPIC, pojo.getId().toString());
        }
    }

    /**
     * 删除文章与素材的关系
     * @param wmNewsId
     */
    private void removeNewsMaterialRelationship(Long wmNewsId) {
        wmNewsMaterialMapper.deleteByMap(Collections.singletonMap("news_id", wmNewsId));
    }

    /**
     * 新闻文章分页查询
     *
     * @param dto
     * @return
     */
    @Override
    public PageResultVo findPage(WmNewsPageReqDto dto) {
        //1. 构建分页参数
        IPage<WmNews> pageInfo = new Page<>(dto.getPage(), dto.getSize());
        //2. 查询条件
        LambdaQueryWrapper<WmNews> lqw = new LambdaQueryWrapper<>();
        //3. 查询
        // 只能查询自己发布的文章
        lqw.eq(WmNews::getUserId, RequestContextUtil.getUserId());
        // 频道
        lqw.eq(null != dto.getChannelId(), WmNews::getChannelId, dto.getChannelId());
        // 关键字, 文章的标题，使用模糊查询
        lqw.like(!StringUtils.isEmpty(dto.getKeyword()), WmNews::getTitle, dto.getKeyword());
        // 状态
        lqw.eq(null != dto.getStatus(), WmNews::getStatus, dto.getStatus());
        // 开始时间
        lqw.ge(null != dto.getBeginPubDate(), WmNews::getPublishTime, dto.getBeginPubDate());
        // 结束时间
        lqw.le(null != dto.getEndPubDate(), WmNews::getPublishTime, dto.getEndPubDate());

        // 查询
        page(pageInfo, lqw);
        //4. 返回结果
        return PageResultVo.pageResult(dto.getPage(),dto.getSize(),pageInfo.getTotal(),pageInfo.getRecords());
    }

    /**
     * 通过id查询
     *
     * @param wmNewsId
     * @return
     */
    @Override
    public WmNews findById(Long wmNewsId) {
        return query().eq("id", wmNewsId).eq("user_id", RequestContextUtil.getUserId()).one();
    }

    /**
     * 删除
     *
     * @param wmNewsId
     */
    @Override
    public void deleteById(Long wmNewsId) {
        // 只能删除登录者自己的文章
        // 查询
        WmNews myWmNews = findById(wmNewsId);
        if(null == myWmNews){
            throw new LeadNewsException(HttpCodeEnum.DATA_NOT_EXIST);
        }
        // 判断状态 如果为9且已发布则不能删除
        if(myWmNews.getStatus() == BusinessConstants.WmNewsConstants.STATUS_PUBLISHED
            && myWmNews.getEnable() == BusinessConstants.WmNewsConstants.ENABLE_ON){
            throw new LeadNewsException("文章已上发布上架，不能删除!");
        }
        // 删除, 不能调用自已的方法
        removeById(wmNewsId);
        //删除文章与素材的关系
        removeNewsMaterialRelationship(wmNewsId);
    }

    /**
     * 文章的上架与下架
     *
     * @param dto
     */
    @Override
    public void downOrUp(DownOrUpDto dto) {
        //1. 查询文章信息, 条件必须是自己的
        Long wmNewsId = dto.getId();
        WmNews wmNews = findById(wmNewsId);
        //2. 判断是否为空
        if(null == wmNews){
            throw new LeadNewsException(HttpCodeEnum.DATA_NOT_EXIST);
        }
        //3. 判断状态是否为已发布9
        if(wmNews.getStatus() != BusinessConstants.WmNewsConstants.STATUS_PUBLISHED){
            throw new LeadNewsException("当前文章不是已发布，不能做上下架操作！");
        }
        //4. 更新上下架的状态。只更新需要更新的字段，无关字段不要更新
        WmNews updatePojo = new WmNews();
        updatePojo.setEnable(dto.getEnable());
        updatePojo.setId(dto.getId());
        updateById(updatePojo);

        // 发消息给kafka, 文章微服监听kafka，更新文章配置表中的id_down的值
        // 文章库中的文章id articleId, 不能是wm_news.id
        dto.setId(wmNews.getArticleId());
        // 转成json字符串
        String jsonMsg = JSON.toJSONString(dto);
        kafkaTemplate.send(BusinessConstants.MqConstants.WM_NEWS_DOWN_OR_UP_TOPIC, jsonMsg);
    }

    /**
     * 文章列表 分页查询 给后台管理人员，人工审核
     *
     * @param dto
     * @return
     */
    @Override
    public PageResultVo findPage4Admin(WmNewsPageReqDto dto) {
        // 先分页查询新闻文章
        // 1. 设置分页参数
        IPage<WmNews> pageInfo = new Page<>(dto.getPage(), dto.getSize());
        // 2. 构建查询条件
        LambdaQueryWrapper<WmNews> lqw = new LambdaQueryWrapper<>();
        lqw.like(!StringUtils.isEmpty(dto.getTitle()), WmNews::getTitle, dto.getTitle());
        lqw.eq(null != dto.getStatus(), WmNews::getStatus, dto.getStatus());
        // 3. 查询
        page(pageInfo, lqw);
        // 4. 得到结果集
        List<WmNewsVo> voList = new ArrayList<>();
        List<WmNews> wmNewsList = pageInfo.getRecords();
        // 提取文章中的作者id
        if(!CollectionUtils.isEmpty(wmNewsList)) {
            List<Long> userIds = wmNewsList.stream().map(WmNews::getUserId).collect(Collectors.toList());
            // 查询作者信息
            List<WmUser> wmUserList = wmUserMapper.selectBatchIds(userIds);
            // map<key=Long, WmUser>
            Map<Long, WmUser> userMap = wmUserList.stream()
                // 每个元素WmUser
                //Function.identity() 元素对象本身
                .collect(Collectors.toMap(WmUser::getId, Function.identity()));
            voList = wmNewsList.stream().map(wmNews->WmNewsVo.build(wmNews, userMap.get(wmNews.getUserId())))
                .collect(Collectors.toList());
        }
        // 构建结果返回
        return PageResultVo.pageResult(dto.getPage(),dto.getSize(),pageInfo.getTotal(), voList);
    }

    /**
     * 人工审核 - 通过
     *
     * @param dto
     */
    @Override
    public void authPass(AuthReqDto dto) {
        WmNews updatePojo = new WmNews();
        updatePojo.setId(dto.getId());
        updatePojo.setStatus(BusinessConstants.WmNewsConstants.STATUS_MANUAL_PASS);

        updateById(updatePojo);
    }

    /**
     * 人工审核 - 驳回
     *
     * @param dto
     */
    @Override
    public void authFail(AuthReqDto dto) {
        WmNews updatePojo = new WmNews();
        updatePojo.setId(dto.getId());
        updatePojo.setStatus(BusinessConstants.WmNewsConstants.STATUS_FAIL);
        updatePojo.setReason(dto.getMsg());
        updateById(updatePojo);
    }

    /**
     * 查询文章详情
     *
     * @param wmNewsId
     * @return
     */
    @Override
    public WmNewsVo getOneVo(Long wmNewsId) {
        // 先查询文章
        WmNews wmNews = getById(wmNewsId);
        // 获取文章作者id
        Long userId = wmNews.getUserId();
        // 查询作者信息
        WmUser wmUser = wmUserMapper.selectById(userId);
        // 构建vo
        WmNewsVo vo = WmNewsVo.build(wmNews, wmUser);
        return vo;
    }

    /**
     * 自媒体文章同步查询
     *
     * @param dto
     * @return
     */
    @Override
    public List<WmNewsVo> list4ArticleSync(WmNewsPageReqDto dto) {
        //1. 调用mapper查询
        List<WmNewsVo> voList = getBaseMapper().select4ArticleSync(dto);
        //2. 返回查询结果
        return voList;
    }

    /**
     * 保存素材与wm_news_material的关系
     * @param pojo
     */
    private void saveWmNewsMaterialRelationship(WmNews pojo) {
        // 得到wm_news_id
        Long wmNewsId = pojo.getId();
        // 得到material_id
        // 素材: 封面 + 内容中的图片
        List<String> coverList = getImageFromCover(pojo); // 3
        // 先去前缀
        coverList = coverList.stream().map(WmNewsServiceImpl::removePrefix).collect(Collectors.toList());
        // 内容中的图片
        List<String> contentImageList = getImagesFromContent(pojo); //5
        // 先去前缀
        contentImageList = contentImageList.stream().map(WmNewsServiceImpl::removePrefix).collect(Collectors.toList());
        // 由于从前端过来的图片都是全路径，要去前缀 url去重
        Set<String> urls = new HashSet<String>();
        urls.addAll(coverList);
        urls.addAll(contentImageList);

        // 查询得到素材对应的id, map<url,material_id>
        Map<String,Long> materialIdMap = getMaterialIdMap(urls);
        // 保存素材关系表
        // 1. 保存封面
        addWmNewsMaterial(wmNewsId, coverList, materialIdMap,BusinessConstants.WmNewsConstants.WM_COVER_REFERENCE);
        // 2. 保存内容
        addWmNewsMaterial(wmNewsId, contentImageList, materialIdMap,BusinessConstants.WmNewsConstants.WM_CONTENT_REFERENCE);
    }


    private void addWmNewsMaterial(Long wmNewsId, List<String> coverList, Map<String, Long> materialIdMap, int type) {
        for (int i = 0; i < coverList.size(); i++) {
            // 构建pojo
            WmNewsMaterial wmNewsMaterial = new WmNewsMaterial();
            // 设置属性值
            wmNewsMaterial.setOrd(i+1);
            // 主图即是封面
            wmNewsMaterial.setType(type);
            // 图片的url, 删除 前缀了的
            String url = coverList.get(i);
            // 通过url获取素材的id
            Long material_id = materialIdMap.get(url);
            wmNewsMaterial.setMaterialId(material_id);
            wmNewsMaterial.setNewsId(wmNewsId);
            // 保存
            wmNewsMaterialMapper.insert(wmNewsMaterial);
        }
    }

    /**
     * 批量查询，通过图片的url查询素材id
     * @param allImageUrls
     * @return
     */
    private Map<String, Long> getMaterialIdMap(Set<String> allImageUrls) {
        Map<String, Long> materialIdMap = new HashMap<>();
        if(!StringUtils.isEmpty(allImageUrls)) {
            // 查询条件
            LambdaQueryWrapper<WmMaterial> lqw = new LambdaQueryWrapper<>();
            // 使用in查询，批量  allImageUrls为空内容
            // mybatisPlus使用forEach拼接sql方法, in (). 报错语法错误
            lqw.in(WmMaterial::getUrl, allImageUrls);
            // 查询
            List<WmMaterial> wmMaterials = wmMaterialMapper.selectList(lqw);
            // 转成map
            materialIdMap = wmMaterials.stream()// 处理每个元素对象WmMaterial
                .collect(Collectors.toMap(WmMaterial::getUrl, WmMaterial::getId));
        }
        return materialIdMap;
    }

    /**
     * 从封面中解析出图片
     * @param pojo
     * @return
     */
    private List<String> getImageFromCover(WmNews pojo) {
        String covers = pojo.getImages();
        List<String> coverList = new ArrayList<>();
        if(!StringUtils.isEmpty(covers)){
            // 封面中的图片是以逗号分割
            String[] coverArray = covers.split(",");
            // 把数组转list
            coverList = Arrays.asList(coverArray);
        }
        return coverList;
    }

    /**
     * 如果前端提交的type(封面的类型)为自动(从文章内容中的图片来)时，处理一下
     * 规则： 内容中的图片数量
     * 数量>=3 时， 封面则有3张图
     * 数量>=1 时， 封面则有1张图
     * 反之 则无图
     * @param pojo
     * @param dto
     */
    private void setWmNewsType(WmNews pojo, WmNewsDtoSave dto) {
        // 为自动
        if (dto.getType() == BusinessConstants.WmNewsConstants.TYPE_AUTO) {
            //内容中的图片数量, 提取内容中的图片
            List<String> contentImages = getImagesFromContent(pojo);
            int size = contentImages.size();
            // 数量>=3 时， 封面则有3张图
            if(size>=3){
                pojo.setType(BusinessConstants.WmNewsConstants.TYPE_MULTIPLE_IMG);
                // 取前3张图，且以逗号分割拼接
                // stream: 源源不断的数据传输（处理）, (流处理器)limit, collect
                // 流中的元素就是集合中的泛型
                String images = contentImages.stream().limit(3).collect(Collectors.joining(","));
                /*StringBuilder sb = new StringBuilder();
                for (int i = 0; i < 3; i++) {
                    sb.append(contentImages.get(i)).append(",");
                }
                sb.setLength(sb.length()-1); // 去最后的逗号
                sb.toString();*/
                pojo.setImages(images);
            }else if(size >=1) {
                // 数量>=1 时， 封面则有1张图
                pojo.setType(BusinessConstants.WmNewsConstants.TYPE_SINGLE_IMG);//封面是单图
                pojo.setImages(contentImages.get(0));
            }else {
                // 反之 则无图
                pojo.setType(BusinessConstants.WmNewsConstants.TYPE_NONE_IMG);
                pojo.setImages("");
            }
        }
    }

    /**
     * 提取内容中的图片
     * @param pojo
     * @return
     */
    private List<String> getImagesFromContent(WmNews pojo) {
        String content = pojo.getContent();
        List<String> list = new ArrayList<>();
        if(!StringUtils.isEmpty(content)){
            // 有内容
            List<WmNewsContentNodeDto> nodeList = JSON.parseArray(content, WmNewsContentNodeDto.class);
            list = nodeList.
                // 获取了每个元素, WmNewsContentNodeDto 对象
                    stream().
                // 过滤: 条件满足的留下，不满足的不要了.  元素对象
                // {
                //   "type":"image",
                //   "value": "http://192.168.211.128/group1/M00/00/00/wKjTgGOKub-AWa3PAAa-tr7X9ZA408.png"
                // }
                    filter(node -> node.getType().equals(BusinessConstants.WmNewsConstants.CONTENT_IMAGE))
                // WmNewsContentNodeDto -> String, 取元素对象中的value属性
                .map(WmNewsContentNodeDto::getValue)
                // 收集，用list来装
                .collect(Collectors.toList());
        }
        return list;
    }

    /**
     * 构建wmNews对象
     * @param dto
     * @return
     */
    private WmNews buildWmNews(WmNewsDtoSave dto,Boolean draft) {
        WmNews pojo = new WmNews();
        // 复制属性
        BeanUtils.copyProperties(dto,pojo);
        // 特殊属性处理
        pojo.setUserId(RequestContextUtil.getUserId());// 登录用户即为文章作者id
        // 创建时间
        if(null == dto.getId()){
            // 第一次提交
            pojo.setCreatedTime(LocalDateTime.now());
        }
        // submitTime由draft来判断
        if(!draft){
            // 草稿没有提交时间
            // 不为草稿就是提交待审核
            pojo.setSubmitedTime(LocalDateTime.now());
        }else{
            pojo.setSubmitedTime(null);// 草稿，不需要提交审核时间, 不能直接设置为null，mybatisplus不更新属性值为null的字段, 使用UpdateWrapper就可以
        }
        // 不能使用提交对象dto中的status值，原因：用户可以传status=8(审核通过)，这是一个bug
        pojo.setStatus(draft? BusinessConstants.WmNewsConstants.STATUS_DRAFT:
            BusinessConstants.WmNewsConstants.STATUS_SUBMIT);
        if(null == pojo.getPublishTime() && draft == false){
            // 默认发布时间
            pojo.setPublishTime(LocalDateTime.now());
        }
        // 封面，类型不一样，无法复制
        if(!CollectionUtils.isEmpty(dto.getImages())){
            // 把一个集合用逗号分割的方式拼接起来成字符串
            String images = String.join(",", dto.getImages());
            pojo.setImages(images);
        }
        // 默认为上架
        pojo.setEnable(BusinessConstants.WmNewsConstants.ENABLE_ON);
        return pojo;
    }

    /**
     * 删除http://ip:port/ 这个前缀
     * @param url
     * @return
     */
    private static String removePrefix(String url){
        //http://192.168.211.128:8080/group1/M00/00/00/wKjTgGK4BGuASyi-AAiWSBNgF8M620.png
        //http://192.168.211.128/group1/M00/00/00/wKjTgGK4BGuASyi-AAiWSBNgF8M620.png
        //http://127.0.0.1/group1/M00/00/00/wKjTgGK4BGuASyi-AAiWSBNgF8M620.png
        // 不支持http://localhost:port/
        return url.replaceAll("http://((\\d){1,3}\\.){3}(\\d){1,3}:?(\\d){0,5}\\/", "");
    }

    public static void main(String[] args) {
        String str1 = "http://192.168.211.128:8080/group1/M00/00/00/wKjTgGK4BGuASyi-AAiWSBNgF8M620.png";
        System.out.println(WmNewsServiceImpl.removePrefix(str1));
        str1 = "http://192.168.211.128/group1/M00/00/00/wKjTgGK4BGuASyi-AAiWSBNgF8M620.png";
        System.out.println(WmNewsServiceImpl.removePrefix(str1));
        str1 = "http://127.0.0.1/group1/M00/00/00/wKjTgGK4BGuASyi-AAiWSBNgF8M620.png";
        System.out.println(WmNewsServiceImpl.removePrefix(str1));
    }
}
